--@author Oleg Mayevskiy <mayevskiy.oleg@googlemail.com> (233197)

require("stack")
require("los_utils")



--debug messages on/off
_debug=false
_ddebug=false
_dddebug=false




--global temp table for self referencing while a class definition
_gtmp = {}
--disallow accessing of all unknown global variables
setmetatable(_G,{
	__index = function(t,k)
			_gtmp[k]=k
			return k
		end
})


--global definition of base types
String = ''
Number = 0
Boolean = false
Table = {}


_newSelf={
	active=false
}
setmetatable(_newSelf,{
__tostring = function(v)
	return tostring(v._self)..'	'..tostring(v.active)
end
})

isBaseCall=false

	-------------------------------
	-- 		SUPER BEGIN			 --
	-------------------------------

super = {}
_super_mt = {}
_super_mt.__index = function(t, k)
	printd('#### super index begin ####')
	local sv = _stack:peek()
	if (isNil(selfClass)) then
		ierror('called super, but stack is empty')
	end

	local selfClass = sv["class"]
	local selfObject = sv["self"]
	printd('super:',k,' for class:',nameOf(selfClass))

	local superClass = _super(selfClass);
	if(not isNil(superClass)) then
		printd('call',k,' on super class',nameOf(superClass),'instead of class ',nameOf(selfClass))
		local _sex, _sexClass = _methodExists(k,superClass)
		if(not _sex) then
			error('Method '..k..' does not exist in super class '..nameOf(superClass))
		end
		return superClass[k]
	elseif(isRoleObject(selfObject)) then
		return fields(_roleObject(selfObject))[k]
	else
		return fields(selfObject)[k]
	end
	printd('#### super index end ####')
end
setmetatable(super, _super_mt);

	-------------------------------
	-- 		SUPER END			 --
	-------------------------------

	-------------------------------
	-- 		BASE BEGIN			 --
	-------------------------------

base = {}
_base_mt = {}
_base_mt.__index = function(t, k)
	printd('#### base index begin ####')
	local sv = _stack:peek()
	if (isNil(selfClass)) then
		ierror('base super, but stack is empty')
	end

	local selfClass = sv["class"]
	printd('in base, selfClass: ',selfClass)
	local selfObject = sv["self"]
	printd('in base, selfObject: ',selfObject)
	printd('base:'..k..' for class:'..nameOf(selfClass))

	local baseObject = {};
	if(isRoleObject(selfObject)) then
		baseObject = _baseObject(selfObject);
	else
		baseObject = selfObject
	end

	printd('baseObject: ',baseObject)
	--find next active role after current role
	local roles = _roles(baseObject)
	local i = table.getn(roles)
	printd('roles size: ',i, '_isActive(roles[i])',_isActive(roles[i]),'selfObject == roles[i]',selfObject == roles[i],'selfObject',selfObject,'roles[i]',roles[i])
	--find position of current role object
	while( (i>0) and (not (selfObject == roles[i]))) do
		i = i - 1
	end

	printd('current role position: ',i)

	--previous search stopped at current role object position
	--so start searching at next object
	i = i-1

	--if next role object position is greater equal then 1
	--then there could be one more active role object else not
	if(i >= 1) then

		while(i>0 and not _isActive(roles[i]) ) do
			i = i - 1
		end

	end

	printd('last position of i',i)
	--found if i>0
	--if found then call this role


	--if(isRoleObject(selfObject)) then
	--	local fex, fexc = _fieldExists(k, _G[iRoleClassName(nameOf(_role(selfObject)))])
	--	if(fex) then
	--		return selfObject[k]
	--	end
	--else
	--	local fex, fexc = _fieldExists(k, class(selfObject))
	--	if(fex) then
	--		return selfObject[k]
	--	end
	--end



	if(i>0) then
		printdd('in base, delegate to role',roles[i])
		--return getmetatable(roles[i]).__index(roles[i],k)
		_newSelf.self=roles[i]
		_newSelf.active=true
		isBaseCall = true
		return roles[i][k]
	--else call base object
	else
		isBaseCall = true
		printdd('in base, delegate to base object', baseObject)
		--return getmetatable(baseObject).__index(baseObject,k)
		_newSelf.self=baseObject
		_newSelf.active=true

		return baseObject[k]
	end
	printd('#### base index end ####')

end
setmetatable(base, _base_mt);

	-------------------------------
	-- 		BASE END			 --
	-------------------------------


function Class(classTable)


	--classTable[1]: class name
	local className = classTable[1]
	local superClass = classTable[2]



	--validation of class definition
	if( isNil(classTable) or isNil(className)) then
		error("Invalid class definition. A valid class definition must have at least a class name, e.g: Class{'MyClass'}")
	elseif(not isString(className)) then
		error("Invalid class name. String type expected, but it is of type "..type(className))
	elseif(not isNil(rawget(_G,className)) and isNil(_gtmp[className])) then
		error("class "..className.." is already definied")
	end


	--metatable for created class and object
	local mt = {}

	--new cleated class

	local newclass = {
		_name = className,
		_fields = {},
		_methods = {},
		--identifies this table as class
		_isClass = true;
	}

	--classTable[2]: super class reference (not name!)
	if(superClass) then
		if( isClass(superClass) ) then
			newclass._super = superClass
		else
			error('wrong super class: must be an allready definied class (but '..superClass..' of type '..type(superClass)..' was given)')
		end
	end






	--principe for __index and __newindex: never write to table itself, this meta table function will be only triggered if the key does not exists!
	--methods should be lookuped in classes!
	--we don't know what of type k is, so we must search for it in different tables, fields are priorised, so search here at first


	mt.__index = function(t, k)


		--methods lookup: each object share methods in his class and super class,
		-- so we have no direct methods in the object table
		-- and must lookup in objects _class field

		--lookup for objects
		if( isObject(t) ) then
			printd('#### object index begin ####')
			--look up field, return if we find it directly
			if( not isNil(fields(t)) and not isNil(fields(t)[k]) ) then
				return fields(t)[k]
			--lookup in class of the object
			else


				local roles = _roles(t)
				printd('roles size',table.getn(roles))
				local activeRoleObject = _peekActiveRoleObject(roles)

				local _rex=false
				local _rexClass={}
				if( not isNil(activeRoleObject) ) then
					_rex, _rexClass = _methodExists(k,class(_roleObject(activeRoleObject)))
					if(not _rex) then printd('activeRoleObject does not override',k) end
				end


				printd('activeRoleObject check before if', isBaseCall, activeRoleObject, _rex)
				if( (not isBaseCall) and (not isNil(activeRoleObject)) and _rex) then
					printd('activeRoleObject found')
					_newSelf.self=activeRoleObject
					_newSelf.active=true
					return getmetatable(_G[iRoleClassName(nameOf(_role(activeRoleObject)))]).__index(_G[iRoleClassName(nameOf(_role(activeRoleObject)))],k)
					--local roleClass = _G[iRoleClassName(nameOf(_role(activeRoleObject)))]
					--return roleClass[k]
				else
					isBaseCall=false
					printd('activeRoleObject not found', isBaseCall, activeRoleObject, _rex)
					local classLookup = class(t)[k]
					--if not found in class ob object, then produce an error
					--non existing fields or methods are not allowed
					if( isNil(classLookup) ) then
						error('no field or method '..k..' in class '..nameOf(class(t))..' found')
					elseif ( isClass(classLookup) ) then
						return nil
					else
						return classLookup
					end
				end
			end

		--lookup for classes
		elseif( isClass(t) ) then
			printd('#### class object index begin ####')
			--look up field, same as by object, but return nil if field is a class
			if( not isNil(fields(t)) and not isNil(fields(t)[k]) ) then
				return fields(t)[k]
			--lookup method
			elseif( not isNil(methods(t)) and not isNil(methods(t)[k])) then
				return methods(t)[k]
			--if not found directly in class, try to find it in super class, if one exists
			elseif( not isNil(_super(t)) ) then
				return _super(t)[k]
			--else just return nil, no error on class lookup!
			else
				return nil
			end


		else
			ierror('class or object expected')
		end



	end

	mt.__tostring= function(v)
		if(isObject(v)) then
			local className = nameOf(class(v))
			return 'object of class '..className
		elseif(isClass(v)) then
			local className = nameOf(v)
			return 'class '..className
		else
			ierror('object or class expected')
		end
	end

	mt.__newindex = function(t, k,v)
		--print('newindex on',t,k,v)
		--if(type(v)~='nil' and t[k]~=nil and type(t[k])~=type(v)) then
		--	error(k..' is of type '..type(t[k])..', but you try set it to type '..type(v))

		--lookup for objects
		if( isObject(t) ) then
			printd('#### object newindex begin ####')
			--adding function to objects is not allowed
			if(isFunction(v)) then
				error('adding functions to objects is not permited')
			else
				local fieldType = class(t)[k]

				--if not found, then error
				if(isNil(fieldType)) then
				    --printTable(fields(class(t)))
					error('dynamic setting of new fields is not allowed (field '..k..' not found)')
				--overriding of functions is not allowed for objects
				elseif(isFunction(fieldType)) then
					error(k..' is a function, overriding of function values on objects is not allowed')
				--need a type checking only for not nil values
				elseif( not isNil(v)) then
					--general type mismatch
					if( type(fieldType)~=type(v) ) then
						error(k..' is of type '..type(fieldType)..', but you try set it to '..type(v))
					--if both types are tables, we need special class type checking
					elseif( isClass(fieldType) ) then
						if( not isObject(v) and not(isRoleObject(v))) then
							error(k..' is of type '..nameOf(fieldType)..', but you try set it to '..type(v))
						end
						--v is object or roleobject
						if( isObject(v) and not isCompatible(fieldType,class(v))) then
							error(k..' is of type '..nameOf(fieldType)..', but you try set it to '..nameOf(class(v)))
						end

						if( isRoleObject(v) and not isCompatible(fieldType,class(_roleObject(v)))) then
							error(k..' is of type '..nameOf(fieldType)..', but you try set it to '..nameOf(class(v)))
						end

					end
				end

				--all checks passed, set new field value
				fields(t)[k] = v


			end

		--lookup for classes
		elseif( isClass(t) ) then
			printd('#### class newindex begin ####')
			if(isFunction(v)) then
				--test if method allready exists
				if(not isNil(methods(t)[k])) then
					error('method '..k..' of class '..nameOf(t)..' already exists, double declaration ist not allowed!')
				end
				--method does not exists, so create new one
				--overriding is allowed, no extra checks in super class needed
				--methods(t)['_intern_'..k] = v
				-- SUPER METHOD WRAPPER
				-- BEGIN
				methods(t)[k] = function(_this, ...)
					--printd('push class '..nameOf(t))
					local sv ={}

					if(_newSelf.active) then
						printddd('set self to',_newSelf.self)
						sv["self"]=_newSelf.self
						_newSelf.active=false
					elseif( isObject(_this) or isRoleObject(_this)) then
						sv["self"]=_this
					else
						sv["self"]=_stack:peek()["self"]
					end
					printdd('self: ',sv["self"])
					sv["class"]=t
					_stack:push(sv)
					printd('call function ', v, ' of class ', nameOf(t))
					local _result = {v(sv["self"],unpack(arg))}
					--printd('pop class '..nameOf(t))
					_stack:pop()
					return unpack(_result)
				end
				-- SUPER METHOD WRAPPER
				-- END

			else
				error('dynamic setting of new fields for classes is not allowed')
			end

		else
			ierror('class or object expected')
		end

	end

	--default constructor for all classes
	function newclass:create()
		local o = {}

		o._roles={}
		--with the class reference we can find methods
		o._class = self
		--fields must be copied by value to object table


		--recursive class traversation, i want the super fields to be set first
		local function getFields(c)
			local superClass = _super(c)
			if( not isNil(superClass)) then
				local superFields = getFields(superClass)
				return tableMerge(superFields, tableCopy(fields(c)))
			else
				return tableCopy(fields(c))
			end
		end

		--copy fields from class tree
		local fields = getFields(self)

		--set class fields to nil
		for k,v in pairs(fields) do
			if( isClass(v) ) then
				fields[k] = nil
			end
		end

		o._fields = fields



		--methods don't exists in objects!

		setmetatable(o,getmetatable(self))
		return o
	end

	setmetatable(newclass, mt)
	--set Parameters, parameters are identified but they key type, which is allways string
	for k,v in pairs(classTable) do
		if(isString(k)) then
			--lookup field in super class
			--if( not isNil(_super(newclass)) and not isNil(_super(newclass)[k]) ) then
			local _fieldExists,_fieldExistsClass=_fieldExists(k,newclass)
			printddd(v)
			printddd(_gtmp[v])
			if( _fieldExists) then
				error('overriding of super fields is not allowed ('..k..' is already definied in class '..nameOf(_fieldExistsClass)..')')
			elseif( (className == v or ( isString(v) and className == iRoleClassName(v))) and not isNil(_gtmp[v])) then

			--className == iRoleClassName(v)
				rawset(fields(newclass), k, newclass)
				_gtmp[v]=nil
			elseif(not isNil(_gtmp[v])) then
				error(v..' is an unknown type. Known types are: Boolean, String, Number or some user defined class.')
			else
				rawset(fields(newclass), k, v)
			end
		end
	end



	--make new clas available in global space
	_G[className] = newclass



end


function Role(roleTable)

	printd('#### role definition begin ####')
	local roleName = roleTable[1]
	printd('role name: ',roleName)

	local superRole = roleTable[2]
	if(isNil(superRole)) then
		printd('no super role')
	else
		printd('super role: ',superRole)
	end

	local baseClass = roleTable[3]
	printd('base class: ',baseClass)

	local roleParameters = {}

	for k,v in pairs(roleTable) do
		if(isString(k)) then
			roleParameters[k]=v
		end
	end

	if(not table.empty(roleParameters)) then
		printd('role parameters:')
		for k,v in pairs(roleParameters) do
			printd(k)
		end
	end



	--validation of role definition
	if( isNil(roleTable) or isNil(roleName) ) then
		error("Invalid role definition. No role name provided")
	elseif(not isString(roleName)) then
		error("Invalid role name. String type expected, but it is of type "..type(roleName))
	elseif(not isNil(rawget(_G,roleName)) and isNil(_gtmp[roleName])) then
		error("role "..roleName.." is already definied")
	elseif(not isNil(superRole) and not isRole(superRole)) then
		error("Invalid super role")
	elseif(isNil(baseClass) or not isClass(baseClass)) then
		error("missing base class in role definition")
	elseif(not isNil(superRole) and not isCompatible(_baseClass(superRole),baseClass)) then
		error("base class of super role is not compatible to base class of current role")
	end

	--introduce an internal class for each role
	--classes are available global, so no need to save it inside a role
	local _Class_input_table={}
	_Class_input_table[1]=iRoleClassName(roleName)
	if(not isNil(superRole)) then
		_Class_input_table[2]=_G[iRoleClassName(nameOf(superRole))]
	end


	for k,v in pairs(roleParameters) do
		local _fieldExists,_fieldExistsClass=_fieldExists(k,baseClass)
		if( _fieldExists) then
			error('overriding of base class fields is not allowed ('..k..' is already definied in class '..nameOf(_fieldExistsClass)..')')
		end

		_Class_input_table[k]=v
	end

	Class(_Class_input_table)



	local newrole = {
		_name = roleName,
		_baseClass = baseClass,
		--identifies this table as role
		_isRole = true;
	}

	--default constructor for all classes
	function newrole:create(baseObject)
		printd('#### create role object begin ####')
		if(isNil(baseObject) or not isObject(baseObject)) then
			error('basis object parameter expected')
		end

		if( not isCompatible(_baseClass(self), class(baseObject)) ) then
			error('base object class is not compatible to base class of this role')
		end

		local o = {}

		o._role=self
		o._baseObject=baseObject
		o._active=true
		local roleObject = _G[iRoleClassName(nameOf(self))]:create()
		rawset(roleObject, '_roleObjectReverse', o)
		o._roleObject= roleObject

		function o:activate()
			rawset(self,'_active',true)
		end

		function o:deactivate()
			rawset(self,'_active',false)
		end

		local mt = {}

		mt.__tostring = function(v)
			return 'role object of role '..nameOf(_role(v))
		end

		mt.__newindex = function(t,k,v)
			local fex, fexc = _fieldExists(k,class(_roleObject(t)))
			if( fex ) then
				_roleObject(t)[k] = v
			else
				_baseObject(t)[k] = v
			end

		end
		mt.__index = function(t,k)
			printd('#### role object index begin ####')
			--lookup roleObject fields
			if( not isNil(fields(_roleObject(t))) and not isNil(fields(_roleObject(t))[k]) ) then
				return fields(_roleObject(t))[k]
			--lookup baseObject fields
			elseif( not isNil(fields(_baseObject(t))) and not isNil(fields(_baseObject(t))[k]) ) then
				return fields(_baseObject(t))[k]
			--else lookup base object
			else
				local _bex, _bexClass = _methodExists(k,class(_baseObject(t)))
				if(_bex) then printd('found method',k,'in base object') end
				local _rex, _rexClass = _methodExists(k,class(_roleObject(t)))
				if(_rex) then printd('found method',k,'in role object') end
				if( isBaseCall or (_rex and not _bex)) then
					isBaseCall=false
					printd('role method exists but not in base class')
					return getmetatable(_roleObject(t)).__index(_roleObject(t),k)
				else
					printd('lookup method in base class')
					return getmetatable(_baseObject(t)).__index(_baseObject(t),k)
					--result= _baseObject(t)[k]
				end

			end
			--printd('#### role object index end ####')
			--return result
		end


		setmetatable(o,mt)


		local roindex = table.getn(_roles(baseObject))+1
		printd('role object index',roindex)
		rawset(_roles(baseObject), roindex, o)


		printd('#### create role object end ####')
		return o
	end

	local mt = {}

	mt.__index=_G[iRoleClassName(roleName)]
	mt.__newindex=_G[iRoleClassName(roleName)]

	setmetatable(newrole,mt)
	_G[roleName] = newrole
	printd('#### role definition end ####')
end
